package network;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.cert.X509Certificate;

import log.Log;
import database.Division;
import database.Doctor;
import database.Government;
import database.Individual;
import database.Nurse;
import database.Patient;
import database.Storage;

public class Server extends Thread {

	private Storage storage;

	private SSLServerSocket serverSocket;
	private String trustStorePath = "certificates/serverTrustStore";
	private String trustStorePassword = "password";
	private String keyStorePassword = "password";
	private String keyStorePath = "certificates/serverKeyStore";

	private String fileName = "data.storage";
	private int port = 40000;

	private Pattern pattern = Pattern.compile("([^\"]\\S*|\".+?\")\\s*");
	private BufferedReader userIn = new BufferedReader(new InputStreamReader(
			System.in));
	private Thread userInThread = new Thread() {
		public void run() {
			while (true) {
				try {
					parse(userIn.readLine());
				} catch (Exception e) {
					Log.log(Log.LOG_EXCEPTION, e.getMessage());
				}
			}
		}
	};

	public Server() {
		System.setProperty("javax.net.ssl.trustStore", trustStorePath);
		System.setProperty("javax.net.ssl.trustStorePassword",
				trustStorePassword);

		storage = new Storage(new File(fileName));
		SSLContext ctx;
		KeyManagerFactory kmf = null;
		TrustManagerFactory tmf = null;
		KeyStore ks;
		KeyStore ts;
		try {
			ctx = SSLContext.getInstance("TLS");

			ks = KeyStore.getInstance("JKS");
			ks.load(new FileInputStream(keyStorePath),
					keyStorePassword.toCharArray());
			kmf = KeyManagerFactory.getInstance("SunX509");

			ts = KeyStore.getInstance("JKS");
			ts.load(new FileInputStream(trustStorePath),
					trustStorePassword.toCharArray());
			tmf = TrustManagerFactory.getInstance("SunX509");

			tmf.init(ts);
			kmf.init(ks, keyStorePassword.toCharArray());

			TrustManager[] tm = tmf.getTrustManagers();
			ctx.init(kmf.getKeyManagers(), tm, null);

			SSLServerSocketFactory factory = ctx.getServerSocketFactory();
			serverSocket = (SSLServerSocket) factory.createServerSocket(port);
			serverSocket.setNeedClientAuth(true);
			
			String[] goodCiphers ={"SSL_RSA_WITH_RC4_128_SHA"};
			serverSocket.setEnabledCipherSuites(goodCiphers);
			
			// serverSocket.bind(new java.net.InetSocketAddress(port));
		} catch (Exception e) {
			Log.log(Log.LOG_EXCEPTION, e.getMessage());
		}
		userInThread.start();
		start();
	}

	public Storage getStorage() {
		return storage;
	}

	public void run() {
		while (true) {
			try {
				serverSocket.setNeedClientAuth(true);
				SSLSocket socket = (SSLSocket) serverSocket.accept();
				
				SSLSession session = socket.getSession();
				X509Certificate cert = (X509Certificate) session
						.getPeerCertificateChain()[0];
				String subject = cert.getSubjectDN().getName();
				Individual ind = initializeUser(subject);
				new ServerConnection(this, socket, ind);
				Log.log(Log.LOG_INFO, "User " + ind.getName()
						+ " authenticated successfully.");
			} catch (IOException e) {
				Log.log(Log.LOG_ERROR, "Server died: " + e.getMessage());
			}
		}
	}

	private Individual initializeUser(String subject) {
		String name = subject.substring(subject.indexOf('=') + 1,
				subject.indexOf(','));
		Individual ind = storage.getIndividual(name);
		if (ind == null) {
			createNewUser(subject);
			ind = storage.getIndividual(name);
		}
		return ind;
	}

	private void createNewUser(String subject) {
		System.out.println(subject);
		Map<String, String> userdata = new HashMap<String, String>();
		for (String str : subject.split(", ")) {
			String[] stra = str.split("=");
			userdata.put(stra[0], stra[1]);
		}
		String name = userdata.get("CN");
		Division division = new Division(userdata.get("OU"));
		if (name.contains("Dr.")) {
			getStorage().addIndividual(new Doctor(name, division));
		} else if (userdata.get("OU").toLowerCase().contains("government")) {
			getStorage().addIndividual(new Government(name));
		} else if (userdata.get("OU").toLowerCase().contains("division")) {
			getStorage().addIndividual(new Nurse(name, division));
		} else {
			getStorage().addIndividual(new Patient(name, null));
		}
	}

	private void parse(String cmd) {
		List<String> arg = new ArrayList<String>();
		Matcher m = pattern.matcher(cmd);
		while (m.find())
			arg.add(m.group(1).replace("\"", ""));
		if (arg.get(0).equals("help")) {
			System.out.println(" - users");
			System.out.println(" - log");
			System.out.println(" - assign");
		} else if (arg.get(0).equals("users")) {
			for (Individual ind : getStorage().getIndividuals())
				System.out.println(ind);
		} else if (arg.get(0).equals("log")) {
			try {
				Log.printLog(System.out);
			} catch (IOException e) {
			}
		} else if (arg.get(0).equals("assign")) {
			if (arg.size() == 1 || arg.get(1).equals("-?")) {
				System.out.println("assign <doctor> <patient>");
				return;
			}
			if (arg.size() < 3) {
				System.out.println("Too few arguments given.");
				return;
			}
			Individual doctor = getStorage().getIndividual(arg.get(1));
			Individual patient = getStorage().getIndividual(arg.get(2));
			if (!(doctor instanceof Doctor)) {
				System.out.println(arg.get(1) + " is not a doctor.");
				return;
			}
			if (!(patient instanceof Patient)) {
				System.out.println(arg.get(2) + " is not a patient.");
				return;
			}
			((Doctor)doctor).getPatients().add((Patient)patient);
			System.out.println("Assigned " + arg.get(1) + " to " + arg.get(2) + ".");
			getStorage().saveToFile();
		}
	}

	public static void main(String[] args) {
		Server server = new Server();
	}

}
